<!--
 * @Author: Penk
 * @LastEditors: Penk
 * @LastEditTime: 2022-12-07 17:42:20
 * @FilePath: \web面试题\对象深拷贝.html
 * @email: 492934056@qq.com
-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>对象深拷贝</title>
  </head>

  <body>
    对象深拷贝

    <script>
      let obj = {
        name: 'penk',
        age: 30,
        boo: true,
        n: null,
        un: undefined,
        sy: Symbol('penk value'),
        // [Symbol("penk key")]: 0,
        // big: 10n,
        child: {
          name: 'penk son'
        },
        arr: [1, { a: 1, aa: { a: 11 } }, 3, 4],
        reg: /^\d+$/,
        fn: function () {
          console.log('name:', this.name);
        },
        time: new Date(),
        err: new Error()
      };
      console.log('obj:', obj);

      let obj2 = JSON.parse(JSON.stringify(obj));
      console.log('JSON.parse(JSON.stringify(obj)): ', obj2);
      // JSON.stringify()
      //    + 无法转换 bigint类型
      //    + 对属性值类型是 undefined symbol function 等类型，会丢失
      //    + 对属性值类型是 error regexp 等类型，会变为空对象
      //    + 对属性值类型是 date 等类型，会变成字符串

      // 深拷贝函数
      // 避免对象中存在重复应用的优化方案
      // 通过set集合的方式，obj不同才会进行操作
      function deepCopy(obj, treated = new WeakMap()) {
        // typeof 不是object，可以直接拷贝~
        // 下方数据类型可以直接返回
        // Undefined|Number|String|Boolean|Null|Function|Symbol
        // 1. Object跟Array 需要递归处理
        // 2. Regexp Error Date 数据问题需要重新NEW
        if (obj == null || typeof obj !== 'object') return obj;

        // 对象的类型
        let string = Object.prototype.toString.call(obj);

        // 对象的构造函数
        let ctor = obj.constructor;
        // 如果有这个obj这个键名，则直接返回键值
        if (treated.has(obj)) return treated.get(obj);

        let newObj = {};

        // 是个复合数据类型，放的是地址
        treated.set(obj, newObj);

        if (string.includes('Object')) {
          // for of 不能遍历普通对象，只能遍历iterator 对象
          for (let i in obj) {
            newObj[i] = deepCopy(obj[i], treated);
          }

          return newObj;
        } else if (string.includes('Array')) {
          // 是数组
          newObj = [];
          for (let i = 0; i < obj.length; i++) {
            newObj.push(deepCopy(obj[i], treated));
          }
        } else if (string.includes('RegExp')) {
          // 是正则对象
          newObj = new ctor(obj);
        } else if (string.includes('Date')) {
          // 是日期对象
          newObj = new ctor(obj);
        } else if (string.includes('Error')) {
          // 是Error对象
          newObj = new ctor(obj.message);
        }
        // else if (string.includes("Symbol")) {
        //   // 是Symbol 不是一个object对象，所以直接可以赋值
        //   newObj = Symbol(obj.description);
        // } else if (string.includes("Function")) {
        //   console.log('function...');
        //   // 是方法 不是一个object对象，所以直接可以赋值
        //   newObj = function (...arg) {
        //     obj.call(this, ...arg);
        //   };
        // }
        else {
          console.log('????????????????');
        }

        return newObj;
      }

      // 制造bug，重复引用
      obj.obj = obj;

      // 深拷贝
      let obj3 = deepCopy(obj);
      console.log('obj3:', obj3);
      // 修改对象
      obj3.name = 'penk666';
      obj3.fn();
      obj.fn();

      // 这里是对obj.fn 整个进行重写了，不是修改子属性，所以是复合数据类型也没用
      obj.fn = () => {
        console.log('xxx');
      };
      obj3.fn();
      obj.fn();
    </script>
  </body>
</html>
